Path and CrossSection
Contents
Path and CrossSection#
You can create a Path in gdsfactory and extrude it with an arbitrary CrossSection.
Lets create a path:
Create a blank
Path.Append points to the
Patheither using the built-in functions (arc(),straight(),euler()…) or by providing your own lists of pointsSpecify
CrossSectionwith layers and offsets.Extrude
Pathwith aCrossSectionto create a Component with the path polygons in it.
Path#
The first step is to generate the list of points we want the path to follow. Let’s start out by creating a blank Path and using the built-in functions to make a few smooth turns.
[1]:
import matplotlib.pyplot as plt
import numpy as np
from gdsfactory.cross_section import Section
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90)) # Circular arc
P.append(gf.path.straight(length=10)) # Straight section
P.append(gf.path.euler(radius=3, angle=-90)) # Euler bend (aka "racetrack" curve)
P.append(gf.path.straight(length=40))
P.append(gf.path.arc(radius=8, angle=-45))
P.append(gf.path.straight(length=10))
P.append(gf.path.arc(radius=8, angle=45))
P.append(gf.path.straight(length=10))
f = P.plot()
2023-02-20 17:46:14.969 | INFO | gdsfactory.config:<module>:50 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 6.43.1
2023-02-20 17:46:15.908 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
2023-02-20 17:46:15.915 | INFO | gdsfactory.pdk:activate:206 - 'generic' PDK is now active
[2]:
p2 = P.copy().rotate()
f = p2.plot()
[3]:
P.points - p2.points
array([[ 0. , 0. ], [ 0.07775818, -0.18109421], [ 0.16015338, -0.36012627], [ 0.24713097, -0.53697746], [ 0.33863327, -0.71153054], [ 0.43459961, -0.88366975], [ 0.53496636, -1.05328096], [ 0.63966697, -1.2202517 ], [ 0.74863202, -1.38447127], [ 0.86178925, -1.54583076], [ 0.97906364, -1.7042232 ], [ 1.10037742, -1.85954356], [ 1.22565016, -2.01168884], [ 1.35479879, -2.16055818], [ 1.48773767, -2.30605285], [ 1.62437867, -2.44807638], [ 1.76463118, -2.58653461], [ 1.9084022 , -2.72133573], [ 2.0555964 , -2.85239035], [ 2.20611618, -2.97961158], [ 2.35986175, -3.10291506], [ 2.51673115, -3.22221903], [ 2.67662037, -3.33744439], [ 2.83942339, -3.44851474], [ 3.00503227, -3.55535642], [ 3.1733372 , -3.6578986 ], [ 3.34422657, -3.75607328], [ 3.51758709, -3.84981537], [ 3.69330379, -3.93906271], [ 3.87126017, -4.02375613], [ 4.05133823, -4.10383946], [ 4.23341856, -4.1792596 ], [ 4.41738044, -4.24996655], [ 4.60310189, -4.31591343], [ 4.79045976, -4.3770565 ], [ 4.97932982, -4.43335522], [ 5.16958684, -4.48477228], [ 5.36110467, -4.53127356], [ 5.55375631, -4.57282824], [ 5.74741403, -4.60940877], [ 5.94194941, -4.64099089], [ 6.13723348, -4.66755366], [ 6.33313674, -4.68907946], [ 6.52952929, -4.70555403], [ 6.72628092, -4.71696644], [ 6.92326117, -4.72330911], [ 7.12033942, -4.72457786], [ 7.317385 , -4.72077183], [ 7.51426725, -4.71189355], [ 7.71085564, -4.69794891], [ 7.90701981, -4.67894715], [ 8.10262968, -4.65490087], [ 8.29755556, -4.62582602], [ 8.4916682 , -4.59174187], [ 8.6848389 , -4.55267103], [ 8.87693955, -4.50863939], [ 9.0678428 , -4.45967617], [ 9.25742205, -4.40581381], [ 9.44555161, -4.34708805], [ 9.63210673, -4.28353781], [ 9.81696372, -4.21520523], [ 10. , -4.14213562], [ 17.07106781, -1.21320344], [ 17.22259744, -1.15062977], [ 17.3745295 , -1.0890412 ], [ 17.52724719, -1.02943054], [ 17.68109502, -0.9728054 ], [ 17.83635884, -0.92019408], [ 17.99324531, -0.87264941], [ 18.15186066, -0.83125013], [ 18.31218874, -0.79709882], [ 18.47406876, -0.77131591], [ 18.63717281, -0.75502883], [ 18.80098407, -0.74935572], [ 18.98113405, -0.75643383], [ 19.16017334, -0.77762453], [ 19.33699811, -0.81279716], [ 19.51051817, -0.86173488], [ 19.67966373, -0.92413597], [ 19.84339193, -0.9996157 ], [ 20.00069333, -1.08770872], [ 20.15059814, -1.18787191], [ 20.29218212, -1.29948772], [ 20.42457237, -1.42186801], [ 20.53639293, -1.54171156], [ 20.6402082 , -1.66856024], [ 20.73644339, -1.80125797], [ 20.82566384, -1.93877567], [ 20.90854811, -2.08020737], [ 20.98586445, -2.22476202], [ 21.05845073, -2.37175194], [ 21.12719755, -2.5205788 ], [ 21.19303416, -2.67071762], [ 21.25691665, -2.82169951], [ 21.31981802, -2.97309339], [ 33.03554677, -31.25736464], [ 33.1091836 , -31.44370627], [ 33.17668408, -31.63235747], [ 33.23797592, -31.82311621], [ 33.29299349, -32.01577822], [ 33.34167789, -32.21013721], [ 33.38397696, -32.40598505], [ 33.41984543, -32.60311201], [ 33.44924488, -32.80130701], [ 33.47214384, -33.00035782], [ 33.48851777, -33.20005129], [ 33.49834914, -33.40017358], [ 33.50162744, -33.60051039], [ 33.49834914, -33.80084721], [ 33.48851777, -34.0009695 ], [ 33.47214384, -34.20066296], [ 33.44924488, -34.39971377], [ 33.41984543, -34.59790877], [ 33.38397696, -34.79503574], [ 33.34167789, -34.99088357], [ 33.29299349, -35.18524256], [ 33.23797592, -35.37790458], [ 33.17668408, -35.56866332], [ 33.1091836 , -35.75731451], [ 33.03554677, -35.94365614], [ 30.10661458, -43.01472396], [ 30.03297776, -43.20106559], [ 29.96547728, -43.38971678], [ 29.90418544, -43.58047552], [ 29.84916786, -43.77313754], [ 29.80048347, -43.96749653], [ 29.75818439, -44.16334436], [ 29.72231592, -44.36047132], [ 29.69291647, -44.55866633], [ 29.67001752, -44.75771713], [ 29.65364359, -44.9574106 ], [ 29.64381221, -45.15753289], [ 29.64053392, -45.35786971], [ 29.64381221, -45.55820652], [ 29.65364359, -45.75832881], [ 29.67001752, -45.95802228], [ 29.69291647, -46.15707309], [ 29.72231592, -46.35526809], [ 29.75818439, -46.55239505], [ 29.80048347, -46.74824289], [ 29.84916786, -46.94260187], [ 29.90418544, -47.13526389], [ 29.96547728, -47.32602263], [ 30.03297776, -47.51467382], [ 30.10661458, -47.70101546], [ 33.03554677, -54.77208327]])
You can also modify our Path in the same ways as any other gdsfactory object:
Manipulation with
move(),rotate(),mirror(), etcAccessing properties like
xmin,y,center,bbox, etc
[4]:
P.movey(10)
P.xmin = 20
f = P.plot()
You can also check the length of the curve with the length() method:
[5]:
P.length()
105.34098399267245
CrossSection#
Now that you’ve got your path defined, the next step is to define the cross-section of the path. To do this, you can create a blank CrossSection and add whatever cross-sections you want to it. You can then combine the Path and the CrossSection using the gf.path.extrude() function to generate a Component:
Option 1: Single layer and width cross-section#
The simplest option is to just set the cross-section to be a constant width by passing a number to extrude() like so:
[6]:
# Extrude the Path and the CrossSection
c = gf.path.extrude(P, layer=(1, 0), width=1.5)
c
extrude_83078f33: uid cb13c3fc, ports ['o1', 'o2'], references [], 1 polygons
Option 2: Linearly-varying width#
A slightly more advanced version is to make the cross-section width vary linearly from start to finish by passing a 2-element list to extrude() like so:
[7]:
# Extrude the Path and the CrossSection
c = gf.path.extrude(P, layer=(1, 0), widths=(1, 3))
c
extrude_a2af4a0b: uid ebb0f4a6, ports ['o1', 'o2'], references [], 1 polygons
Option 3: Arbitrary Cross-section#
You can also extrude an arbitrary cross_section
Now, what if we want a more complicated straight? For instance, in some photonic applications it’s helpful to have a shallow etch that appears on either side of the straight (often called a trench or sleeve). Additionally, it might be nice to have a Port on either end of the center section so we can snap other geometries to it. Let’s try adding something like that in:
[8]:
p = gf.path.straight()
# Add a few "sections" to the cross-section
s1 = gf.Section(width=2, offset=2, layer=(2, 0))
s2 = gf.Section(width=2, offset=-2, layer=(2, 0))
x = gf.CrossSection(
width=1, offset=0, layer=(1, 0), port_names=("in", "out"), sections=[s1, s2]
)
c = gf.path.extrude(p, cross_section=x)
c
extrude_fbf7128f: uid b99ad1f4, ports ['in', 'out'], references [], 3 polygons
[9]:
p = gf.path.arc()
# Combine the Path and the CrossSection
b = gf.path.extrude(p, cross_section=x)
b
extrude_54d01320: uid b1858d41, ports ['in', 'out'], references [], 3 polygons
Building Paths quickly#
You can pass append() lists of path segments. This makes it easy to combine paths very quickly. Below we show 3 examples using this functionality:
Example 1: Assemble a complex path by making a list of Paths and passing it to append()
[10]:
P = gf.Path()
# Create the basic Path components
left_turn = gf.path.euler(radius=4, angle=90)
right_turn = gf.path.euler(radius=4, angle=-90)
straight = gf.path.straight(length=10)
# Assemble a complex path by making list of Paths and passing it to `append()`
P.append(
[
straight,
left_turn,
straight,
right_turn,
straight,
straight,
right_turn,
left_turn,
straight,
]
)
f = P.plot()
Example 2: Create an “S-turn” just by making a list of [left_turn, right_turn]
[11]:
P = gf.Path()
# Create an "S-turn" just by making a list
s_turn = [left_turn, right_turn]
P.append(s_turn)
f = P.plot()
Example 3: Repeat the S-turn 3 times by nesting our S-turn list in another list
[12]:
P = gf.Path()
# Create an "S-turn" using a list
s_turn = [left_turn, right_turn]
# Repeat the S-turn 3 times by nesting our S-turn list 3x times in another list
triple_s_turn = [s_turn, s_turn, s_turn]
P.append(triple_s_turn)
f = P.plot()
Note you can also use the Path() constructor to immediately construct your Path:
[13]:
P = gf.Path([straight, left_turn, straight, right_turn, straight])
f = P.plot()
Waypoint smooth paths#
You can also build smooth paths between waypoints with the smooth() function
[14]:
points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])
plt.plot(points[:, 0], points[:, 1], ".-")
plt.axis("equal")
(17.5, 72.5, 8.5, 41.5)
[15]:
points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])
P = gf.path.smooth(
points=points,
radius=2,
bend=gf.path.euler, # Alternatively, use pp.arc
use_eff=False,
)
f = P.plot()
Waypoint sharp paths#
It’s also possible to make more traditional angular paths (e.g. electrical wires) in a few different ways.
Example 1: Using a simple list of points
[16]:
P = gf.Path([(20, 10), (30, 10), (40, 30), (50, 30), (50, 20), (70, 20)])
f = P.plot()
Example 2: Using the “turn and move” method, where you manipulate the end angle of the Path so that when you append points to it, they’re in the correct direction. Note: It is crucial that the number of points per straight section is set to 2 (``pp.straight(length, num_pts = 2)``) otherwise the extrusion algorithm will show defects.
[17]:
P = gf.Path()
P.append(gf.path.straight(length=10, npoints=2))
P.end_angle += 90 # "Turn" 90 deg (left)
P.append(gf.path.straight(length=10, npoints=2)) # "Walk" length of 10
P.end_angle += -135 # "Turn" -135 degrees (right)
P.append(gf.path.straight(length=15, npoints=2)) # "Walk" length of 10
P.end_angle = 0 # Force the direction to be 0 degrees
P.append(gf.path.straight(length=10, npoints=2)) # "Walk" length of 10
f = P.plot()
[18]:
s1 = gf.Section(width=1.5, offset=2.5, layer=(2, 0))
s2 = gf.Section(width=1.5, offset=-2.5, layer=(3, 0))
X = gf.CrossSection(width=1, offset=0, layer=(1, 0), sections=[s1, s2])
component = gf.path.extrude(P, X)
component
extrude_4b5fc8d2: uid a391e7bc, ports ['o1', 'o2'], references [], 3 polygons
Custom curves#
Now let’s have some fun and try to make a loop-de-loop structure with parallel straights and several Ports.
To create a new type of curve we simply make a function that produces an array of points. The best way to do that is to create a function which allows you to specify a large number of points along that curve – in the case below, the looploop() function outputs 1000 points along a looping path. Later, if we want reduce the number of points in our geometry we can trivially simplify the path.
[19]:
def looploop(num_pts=1000):
"""Simple limacon looping curve"""
t = np.linspace(-np.pi, 0, num_pts)
r = 20 + 25 * np.sin(t)
x = r * np.cos(t)
y = r * np.sin(t)
return np.array((x, y)).T
# Create the path points
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90))
P.append(gf.path.straight())
P.append(gf.path.arc(radius=5, angle=-90))
P.append(looploop(num_pts=1000))
P.rotate(-45)
# Create the crosssection
s1 = gf.Section(width=0.5, offset=2, layer=(2, 0))
s2 = gf.Section(width=0.5, offset=4, layer=(3, 0))
s3 = gf.Section(width=1, offset=0, layer=(4, 0))
X = gf.CrossSection(
width=1.5, offset=0, layer=(1, 0), port_names=["in", "out"], sections=[s1, s2, s3]
)
c = gf.path.extrude(P, X)
c
extrude_ee70f2c5: uid e901925e, ports ['in', 'out'], references [], 4 polygons
You can create Paths from any array of points – just be sure that they form smooth curves! If we examine our path P we can see that all we’ve simply created a long list of points:
[20]:
path_points = P.points # Curve points are stored as a numpy array in P.points
print(np.shape(path_points)) # The shape of the array is Nx2
print(len(P)) # Equivalently, use len(P) to see how many points are inside
(1092, 2)
1092
Simplifying / reducing point usage#
One of the chief concerns of generating smooth curves is that too many points are generated, inflating file sizes and making boolean operations computationally expensive. Fortunately, PHIDL has a fast implementation of the Ramer-Douglas–Peucker algorithm that lets you reduce the number of points in a curve without changing its shape. All that needs to be done is when you made a component component() extruding
the path with a cross_section, you specify the simplify argument.
If we specify simplify = 1e-3, the number of points in the line drops from 12,000 to 4,000, and the remaining points form a line that is identical to within 1e-3 distance from the original (for the default 1 micron unit size, this corresponds to 1 nanometer resolution):
[21]:
# The remaining points form a identical line to within `1e-3` from the original
c = gf.path.extrude(p=P, cross_section=X, simplify=1e-3)
c
extrude_4569f743: uid 3513722f, ports ['in', 'out'], references [], 4 polygons
Let’s say we need fewer points. We can increase the simplify tolerance by specifying simplify = 1e-1. This drops the number of points to ~400 points form a line that is identical to within 1e-1 distance from the original:
[22]:
c = gf.path.extrude(P, cross_section=X, simplify=1e-1)
c
extrude_d0657b4a: uid ed5752e4, ports ['in', 'out'], references [], 4 polygons
Taken to absurdity, what happens if we set simplify = 0.3? Once again, the ~200 remaining points form a line that is within 0.3 units from the original – but that line looks pretty bad.
[23]:
c = gf.path.extrude(P, cross_section=X, simplify=0.3)
c
extrude_16753eb9: uid 874a621e, ports ['in', 'out'], references [], 4 polygons
Curvature calculation#
The Path class has a curvature() method that computes the curvature K of your smooth path (K = 1/(radius of curvature)). This can be helpful for verifying that your curves transition smoothly such as in track-transition curves (also known as “Euler” bends in the photonics world). Euler bends have lower mode-mismatch loss as explained in this
paper
Note this curvature is numerically computed so areas where the curvature jumps instantaneously (such as between an arc and a straight segment) will be slightly interpolated, and sudden changes in point density along the curve can cause discontinuities.
[24]:
straight_points = 100
P = gf.Path()
P.append(
[
gf.path.straight(
length=10, npoints=straight_points
), # Should have a curvature of 0
gf.path.euler(
radius=3, angle=90, p=0.5, use_eff=False
), # Euler straight-to-bend transition with min. bend radius of 3 (max curvature of 1/3)
gf.path.straight(
length=10, npoints=straight_points
), # Should have a curvature of 0
gf.path.arc(radius=10, angle=90), # Should have a curvature of 1/10
gf.path.arc(radius=5, angle=-90), # Should have a curvature of -1/5
gf.path.straight(
length=2, npoints=straight_points
), # Should have a curvature of 0
]
)
f = P.plot()
Arc paths are equivalent to bend_circular and euler paths are equivalent to bend_euler
[25]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
[26]:
P = gf.path.euler(radius=3, angle=90, p=1.0, use_eff=False)
P.append(gf.path.euler(radius=3, angle=90, p=0.2, use_eff=False))
P.append(gf.path.euler(radius=3, angle=90, p=0.0, use_eff=False))
P.plot()
[26]:
[27]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
You can compare two 90 degrees euler bend with 180 euler bend.
A 180 euler bend is shorter, and has less loss than two 90 degrees euler bend.
[28]:
straight_points = 100
P = gf.Path()
P.append(
[
gf.path.euler(radius=3, angle=90, p=1, use_eff=False),
gf.path.euler(radius=3, angle=90, p=1, use_eff=False),
gf.path.straight(length=6, npoints=100),
gf.path.euler(radius=3, angle=180, p=1, use_eff=False),
]
)
f = P.plot()
[29]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
Transitioning between cross-sections#
Often a critical element of building paths is being able to transition between cross-sections. You can use the transition() function to do exactly this: you simply feed it two CrossSections and it will output a new CrossSection that smoothly transitions between the two.
Let’s start off by creating two cross-sections we want to transition between. Note we give all the cross-sectional elements names by specifying the name argument in the add() function – this is important because the transition function will try to match names between the two input cross-sections, and any names not present in both inputs will be skipped.
[30]:
# Create our first CrossSection
s1 = gf.Section(width=2.2, offset=0, layer=(3, 0), name="etch")
s2 = gf.Section(width=1.1, offset=3, layer=(1, 0), name="wg2")
X1 = gf.CrossSection(
width=1.2,
offset=0,
layer=(2, 0),
name="wg",
port_names=("o1", "o2"),
sections=[s1, s2],
)
# Create the second CrossSection that we want to transition to
s1 = gf.Section(width=3.5, offset=0, layer=(3, 0), name="etch")
s2 = gf.Section(width=3, offset=5, layer=(1, 0), name="wg2")
X2 = gf.CrossSection(
width=1,
offset=0,
layer=(2, 0),
name="wg",
port_names=("o1", "o2"),
sections=[s1, s2],
)
# To show the cross-sections, let's create two Paths and
# create Components by extruding them
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
# Place both cross-section Components and quickplot them
c = gf.Component("demo")
wg1ref = c << wg1
wg2ref = c << wg2
wg2ref.movex(7.5)
c
demo: uid 7164497a, ports [], references ['extrude_1', 'extrude_2'], 0 polygons
Now let’s create the transitional CrossSection by calling transition() with these two CrossSections as input. If we want the width to vary as a smooth sinusoid between the sections, we can set width_type to 'sine' (alternatively we could also use 'linear').
[31]:
# Create the transitional CrossSection
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="sine")
# Create a Path for the transitional CrossSection to follow
P3 = gf.path.straight(length=15, npoints=100)
# Use the transitional CrossSection to create a Component
straight_transition = gf.path.extrude(P3, Xtrans)
straight_transition
extrude_63a9d9d6: uid bc617eaf, ports ['o1', 'o2'], references [], 3 polygons
[32]:
wg1
extrude_59f9236f: uid c662201e, ports ['o1', 'o2'], references [], 3 polygons
[33]:
wg2
extrude_b1101b4b: uid 441469c5, ports ['o1', 'o2'], references [], 3 polygons
Now that we have all of our components, let’s connect() everything and see what it looks like
[34]:
c = gf.Component("transition_demo")
wg1ref = c << wg1
wgtref = c << straight_transition
wg2ref = c << wg2
wgtref.connect("o1", wg1ref.ports["o2"])
wg2ref.connect("o1", wgtref.ports["o2"])
c
transition_demo: uid 055a9f65, ports [], references ['extrude_1', 'extrude_2', 'extrude_3'], 0 polygons
Note that since transition() outputs a CrossSection, we can make the transition follow an arbitrary path:
[35]:
# Transition along a curving Path
P4 = gf.path.euler(radius=25, angle=45, p=0.5, use_eff=False)
wg_trans = gf.path.extrude(P4, Xtrans)
c = gf.Component("demo_transition")
wg1_ref = c << wg1 # First cross-section Component
wg2_ref = c << wg2
wgt_ref = c << wg_trans
wgt_ref.connect("o1", wg1_ref.ports["o2"])
wg2_ref.connect("o1", wgt_ref.ports["o2"])
c
demo_transition: uid 135bfdb4, ports [], references ['extrude_1', 'extrude_2', 'extrude_3'], 0 polygons
Variable width / offset#
In some instances, you may want to vary the width or offset of the path’s cross- section as it travels. This can be accomplished by giving the CrossSection arguments that are functions or lists. Let’s say we wanted a width that varies sinusoidally along the length of the Path. To do this, we need to make a width function that is parameterized from 0 to 1: for an example function my_width_fun(t) where the width at t==0 is the width at the beginning of the Path and the width at
t==1 is the width at the end.
[36]:
def my_custom_width_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_width_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 5
return 3 + np.cos(2 * np.pi * t * num_periods)
# Create the Path
P = gf.path.straight(length=40, npoints=30)
# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
s = gf.Section(width=my_custom_width_fun, offset=0, layer=(1, 0))
X = gf.CrossSection(width=3, offset=-6, layer=(2, 0), sections=[s])
# Extrude the Path to create the Component
c = gf.path.extrude(P, cross_section=X)
c
extrude_d5cd1ec2: uid 31bc7e88, ports ['o1', 'o2'], references [], 2 polygons
We can do the same thing with the offset argument:
[37]:
def my_custom_offset_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 3
return 3 + np.cos(2 * np.pi * t * num_periods)
# Create the Path
P = gf.path.straight(length=40, npoints=30)
# Create two cross-sections: one fixed offset, one modulated by my_custom_offset_fun
s = gf.Section(
width=1, offset=my_custom_offset_fun, layer=(2, 0), port_names=["clad1", "clad2"]
)
X = gf.CrossSection(width=1, offset=0, layer=(1, 0), sections=[s])
# Extrude the Path to create the Component
c = gf.path.extrude(P, cross_section=X)
c
extrude_2e1a443c: uid 6c6528b3, ports ['clad1', 'clad2', 'o1', 'o2'], references [], 2 polygons
Offsetting a Path#
Sometimes it’s convenient to start with a simple Path and offset the line it follows to suit your needs (without using a custom-offset CrossSection). Here, we start with two copies of simple straight Path and use the offset() function to directly modify each Path.
[38]:
def my_custom_offset_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 3
return 2 + np.cos(2 * np.pi * t * num_periods)
P1 = gf.path.straight(npoints=101)
P2 = P1.copy() # Make a copy of the Path
P1.offset(offset=my_custom_offset_fun)
P2.offset(offset=my_custom_offset_fun)
P2.mirror((1, 0)) # reflect across X-axis
f = P1.plot()
[39]:
f2 = P2.plot()
Modifying a CrossSection#
In case you need to modify the CrossSection, it can be done simply by specifying a name argument for the cross-sectional element you want to modify later. Here is an example where we name one of thee cross-sectional elements 'myelement1' and 'myelement2':
[40]:
# Create the Path
P = gf.path.arc(radius=10, angle=45)
# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
s = gf.Section(width=1, offset=3, layer=(2, 0), name="waveguide")
X = gf.CrossSection(
width=1,
offset=0,
layer=(1, 0),
port_names=("o1", "o2"),
name="heater",
sections=[s],
)
c = gf.path.extrude(P, X)
c
extrude_cb94c002: uid 60090a78, ports ['o1', 'o2'], references [], 2 polygons
In case we want to change any of the CrossSection elements, we simply access the Python dictionary that specifies that element and modify the values
[41]:
# Create our first CrossSection
s1 = gf.Section(width=2.2, offset=0, layer=(3, 0), name="etch")
s2 = gf.Section(width=1.1, offset=3, layer=(1, 0), name="wg2")
X1 = gf.CrossSection(
width=1.2,
offset=0,
layer=(2, 0),
name="wg",
port_names=("o1", "o2"),
sections=[s1, s2],
)
# Create the second CrossSection that we want to transition to
s1 = gf.Section(width=3.5, offset=0, layer=(3, 0), name="etch")
s2 = gf.Section(width=3, offset=5, layer=(1, 0), name="wg2")
X2 = gf.CrossSection(
width=1,
offset=0,
layer=(2, 0),
name="wg",
port_names=("o1", "o2"),
sections=[s1, s2],
)
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="sine")
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
P4 = gf.path.euler(radius=25, angle=45, p=0.5, use_eff=False)
wg_trans = gf.path.extrude(P4, Xtrans)
# WG_trans = P4.extrude(Xtrans)
c = gf.Component("demo")
wg1_ref = c << wg1
wg2_ref = c << wg2
wgt_ref = c << wg_trans
wgt_ref.connect("o1", wg1_ref.ports["o2"])
wg2_ref.connect("o1", wgt_ref.ports["o2"])
c
demo: uid 0d61a879, ports [], references ['extrude_1', 'extrude_2', 'extrude_3'], 0 polygons
[42]:
len(c.references)
3
Note
Any unnamed section in the CrossSection won’t be transitioned.
If you don’t add any named sections in a cross-section it will give you an error when making a transition
[43]:
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90)) # Circular arc
P.append(gf.path.straight(length=10)) # Straight section
P.append(gf.path.euler(radius=3, angle=-90)) # Euler bend (aka "racetrack" curve)
P.append(gf.path.straight(length=40))
P.append(gf.path.arc(radius=8, angle=-45))
P.append(gf.path.straight(length=10))
P.append(gf.path.arc(radius=8, angle=45))
P.append(gf.path.straight(length=10))
f = P.plot()
[44]:
X1 = gf.CrossSection(width=1, offset=0, layer=(2, 0))
c = gf.path.extrude(P, X1)
c
extrude_eccb20b8: uid d8a5525c, ports ['o1', 'o2'], references [], 1 polygons
[45]:
X2 = gf.CrossSection(width=2, offset=0, layer=(2, 0))
c = gf.path.extrude(P, X2)
c
extrude_f79bdb2d: uid ef5d9cd8, ports ['o1', 'o2'], references [], 1 polygons
For example this will give you an error
T = gf.path.transition(X, X2)
Solution
[46]:
P = gf.path.straight(length=10, npoints=101)
s = gf.Section(width=3, offset=0, layer=gf.LAYER.SLAB90)
X1 = gf.CrossSection(
width=1,
offset=0,
layer=gf.LAYER.WG,
name="core",
port_names=("o1", "o2"),
sections=[s],
)
c = gf.path.extrude(P, X1)
c
extrude_19c35cf5: uid 021a9cc0, ports ['o1', 'o2'], references [], 2 polygons
[47]:
X2 = gf.CrossSection(
width=3, offset=0, layer=gf.LAYER.WG, name="core", port_names=("o1", "o2")
)
c2 = gf.path.extrude(P, X2)
c2
extrude_62380165: uid b5b3ffd6, ports ['o1', 'o2'], references [], 1 polygons
[48]:
T = gf.path.transition(X1, X2)
c3 = gf.path.extrude(P, T)
c3
extrude_ec0d694f: uid 52069ae9, ports ['o1', 'o2'], references [], 1 polygons
[49]:
c4 = gf.Component("demo_transition2")
[50]:
start_ref = c4 << c
trans_ref = c4 << c3
end_ref = c4 << c2
trans_ref.connect("o1", start_ref.ports["o2"])
end_ref.connect("o1", trans_ref.ports["o2"])
ComponentReference (parent Component "extrude_62380165", ports ['o1', 'o2'], origin (20.0, 0.0), rotation 0.0, x_reflection False)
[51]:
c4
demo_transition2: uid 9fd3409f, ports [], references ['extrude_1', 'extrude_2', 'extrude_3'], 0 polygons
cross-section#
You can create functions that return a cross_section in 2 ways:
gf.partialcan customize an existing cross-section for examplegf.cross_section.stripdefine a function that returns a cross_section
What parameters do cross_section take?
[52]:
help(gf.cross_section.cross_section)
Help on cython_function_or_method in module gdsfactory.cross_section:
cross_section(width: 'Union[Callable, float]' = 0.5, offset: 'Union[float, Callable]' = 0, layer: 'LayerSpec' = 'WG', width_wide: 'Optional[float]' = None, auto_widen: 'bool' = False, auto_widen_minimum_length: 'float' = 200.0, taper_length: 'float' = 10.0, radius: 'Optional[float]' = 10.0, sections: 'Optional[Tuple[Section, ...]]' = None, port_names: 'Tuple[str, str]' = ('o1', 'o2'), port_types: 'Tuple[str, str]' = ('optical', 'optical'), min_length: 'float' = 0.01, start_straight_length: 'float' = 0.01, end_straight_length: 'float' = 0.01, snap_to_grid: 'Optional[float]' = None, bbox_layers: 'Optional[List[LayerSpec]]' = None, bbox_offsets: 'Optional[List[float]]' = None, cladding_layers: 'Optional[LayerSpecs]' = None, cladding_offsets: 'Optional[Floats]' = None, info: 'Optional[Dict[str, Any]]' = None, decorator: 'Optional[Callable]' = None, add_pins: 'Optional[Callable]' = None, add_bbox: 'Optional[Callable]' = None, add_center_section: 'bool' = True, mirror: 'bool' = False, name: 'Optional[str]' = None) -> 'CrossSection'
Return CrossSection.
Args:
width: main Section width (um) or function parameterized from 0 to 1.
the width at t==0 is the width at the beginning of the Path.
the width at t==1 is the width at the end.
offset: main Section center offset (um) or function from 0 to 1.
the offset at t==0 is the offset at the beginning of the Path.
the offset at t==1 is the offset at the end.
layer: main section layer.
width_wide: wide waveguides width (um) for low loss routing.
auto_widen: taper to wide waveguides for low loss routing.
auto_widen_minimum_length: minimum straight length for auto_widen.
taper_length: taper_length for auto_widen.
radius: bend radius (um).
sections: list of Sections(width, offset, layer, ports).
port_names: for input and output ('o1', 'o2').
port_types: for input and output: electrical, optical, vertical_te ...
min_length: defaults to 1nm = 10e-3um for routing.
start_straight_length: straight length at the beginning of the route.
end_straight_length: end length at the beginning of the route.
snap_to_grid: can snap points to grid when extruding the path.
bbox_layers: list of layers for rectangular bounding box.
bbox_offsets: list of bounding box offsets.
cladding_layers: list of layers to extrude.
cladding_offsets: list of offset from main Section edge.
info: settings info.
decorator: function to run when converting path to component.
add_pins: optional function to add pins to component.
add_bbox: optional function to add bounding box to component.
add_center_section: whether a section with `width` and `layer`
is added during extrude.
mirror: if True, reflects the offsets.
name: cross_section name.
.. plot::
:include-source:
import gdsfactory as gf
xs = gf.cross_section.cross_section(width=0.5, offset=0, layer='WG')
p = gf.path.arc(radius=10, angle=45)
c = p.extrude(xs)
c.plot()
[53]:
pin = gf.partial(
gf.cross_section.strip,
layer=(2, 0),
sections=(
Section(layer=gf.LAYER.P, width=2, offset=+2),
Section(layer=gf.LAYER.N, width=2, offset=-2),
),
)
[54]:
c = gf.components.straight(cross_section=pin)
c
straight_0686b7f8: uid a86349f3, ports ['o1', 'o2'], references [], 3 polygons
[55]:
pin5 = gf.components.straight(cross_section=pin, length=5)
pin5
straight_43b36685: uid f1e00a1a, ports ['o1', 'o2'], references [], 3 polygons
finally, you can also pass most components Dict that define the cross-section
[56]:
gf.components.straight(
layer=(1, 0),
width=0.5,
sections=(
Section(layer=gf.LAYER.P, width=1, offset=+2),
Section(layer=gf.LAYER.N, width=1, offset=-2),
),
)
straight_fe04a5ce: uid 0d4fcc1b, ports ['o1', 'o2'], references [], 3 polygons
[57]:
# Create our first CrossSection
s1 = gf.Section(width=0.2, offset=0, layer=(3, 0), name="slab")
X1 = gf.CrossSection(
width=0.5, offset=0, layer=(1, 0), name="wg", port_names=("o1", "o2"), sections=[s1]
)
# Create the second CrossSection that we want to transition to
s = gf.Section(width=3.0, offset=0, layer=(3, 0), name="slab")
X2 = gf.CrossSection(
width=0.5, offset=0, layer=(1, 0), name="wg", port_names=("o1", "o2"), sections=[s]
)
# To show the cross-sections, let's create two Paths and
# create Components by extruding them
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
# Place both cross-section Components and quickplot them
c = gf.Component()
wg1ref = c << wg1
wg2ref = c << wg2
wg2ref.movex(7.5)
# Create the transitional CrossSection
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="linear")
# Create a Path for the transitional CrossSection to follow
P3 = gf.path.straight(length=15, npoints=100)
# Use the transitional CrossSection to create a Component
straight_transition = gf.path.extrude(P3, Xtrans)
straight_transition
extrude_d6cef4a0: uid bf09215f, ports ['o1', 'o2'], references [], 2 polygons
[58]:
s = straight_transition.to_3d()
s.show()
[58]:
Waveguides with Shear Faces#
By default, an extruded path will end in a face orthogonal to the direction of the path. In some cases, it is desired to have a sheared face that tilts at a given angle from this orthogonal baseline. This can be done by supplying the parameters shear_angle_start and shear_angle_end to the extrude() function.
[59]:
P = gf.path.straight(length=10)
s = gf.Section(width=3, offset=0, layer=gf.LAYER.SLAB90)
X1 = gf.CrossSection(
width=1,
offset=0,
layer=gf.LAYER.WG,
name="core",
port_names=("o1", "o2"),
sections=[s],
)
c = gf.path.extrude(P, X1, shear_angle_start=10, shear_angle_end=45)
c
extrude_bee5c5d7: uid 94df4e3f, ports ['o1', 'o2'], references [], 2 polygons
By default, the shear angle parameters are None, in which case shearing will not be applied to the face.
[60]:
c = gf.path.extrude(P, X1, shear_angle_start=None, shear_angle_end=10)
c
extrude_0582ba33: uid 6c776f5d, ports ['o1', 'o2'], references [], 2 polygons
Shearing should work on paths of arbitrary orientation, as long as their end segments are sufficiently long.
[61]:
angle = 45
P = gf.path.straight(length=10).rotate(angle)
c = gf.path.extrude(P, X1, shear_angle_start=angle, shear_angle_end=angle)
c
extrude_77c2f42f: uid 7ca6e066, ports ['o1', 'o2'], references [], 2 polygons
For a non-linear path or width profile, the algorithm will intersect the path when sheared inwards and extrapolate linearly going outwards.
[62]:
angle = 15
P = gf.path.euler()
c = gf.path.extrude(P, X1, shear_angle_start=angle, shear_angle_end=angle)
c
extrude_7605560a: uid aac6dbfe, ports ['o1', 'o2'], references [], 2 polygons
The port location, width and orientation remains the same for a sheared component. However, an additional property, shear_angle is set to the value of the shear angle. In general, shear ports can be safely connected together.
[63]:
P = gf.path.straight(length=10)
P_skinny = gf.path.straight(length=0.5)
s = gf.Section(width=3, offset=0, layer=gf.LAYER.SLAB90, name="slab")
X1 = gf.CrossSection(
width=1,
offset=0,
layer=gf.LAYER.WG,
name="core",
port_names=("o1", "o2"),
sections=[s],
)
c = gf.path.extrude(P, X1, shear_angle_start=45, shear_angle_end=45)
c_skinny = gf.path.extrude(P_skinny, X1, shear_angle_start=45, shear_angle_end=45)
circuit = gf.Component("shear_sample")
c1 = circuit << c
c2 = circuit << c_skinny
c3 = circuit << c
c1.connect(port="o1", destination=c2.ports["o1"])
c3.connect(port="o1", destination=c2.ports["o2"])
print(c1.ports["o1"].to_dict())
print(c3.ports["o2"].to_dict())
circuit
{'name': 'o1', 'width': 1.0, 'center': [0.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical', 'shear_angle': 45.0}
{'name': 'o2', 'width': 1.0, 'center': [10.5, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical', 'shear_angle': 45.0}
shear_sample: uid a6d75e1f, ports [], references ['extrude_1', 'extrude_2', 'extrude_3'], 0 polygons
Transitions with Shear faces#
You can also create a transition with a shear face
[64]:
P = gf.path.straight(length=10)
s = gf.Section(width=3, offset=0, layer=gf.LAYER.SLAB90, name="slab")
X1 = gf.CrossSection(
width=1,
offset=0,
layer=gf.LAYER.WG,
name="core",
port_names=("o1", "o2"),
sections=[s],
)
s2 = gf.Section(width=2, offset=0, layer=gf.LAYER.SLAB90, name="slab")
X2 = gf.CrossSection(
width=0.5,
offset=0,
layer=gf.LAYER.WG,
name="core",
port_names=("o1", "o2"),
sections=[s2],
)
t = gf.path.transition(X1, X2, width_type="linear")
c = gf.path.extrude(P, t, shear_angle_start=10, shear_angle_end=45)
c
extrude_301b9865: uid cad734a3, ports ['o1', 'o2'], references [], 2 polygons
This will also work with curves and non-linear width profiles. Keep in mind that points outside the original geometry will be extrapolated linearly.
[65]:
angle = 15
P = gf.path.euler()
c = gf.path.extrude(P, t, shear_angle_start=angle, shear_angle_end=angle)
c
extrude_1db1d2c4: uid cbff09d6, ports ['o1', 'o2'], references [], 2 polygons
bbox_layers vs cladding_layers#
For extruding waveguides you have two options:
bbox_layers for squared bounding box
cladding_layers for extruding a layer that follows the shape of the path.
[66]:
xs_bbox = gf.cross_section.cross_section(bbox_layers=[(3, 0)], bbox_offsets=[3])
w1 = gf.components.bend_euler(cross_section=xs_bbox, with_bbox=True)
w1
bend_euler_caf18727: uid b6fc2152, ports ['o1', 'o2'], references [], 2 polygons
[67]:
xs_clad = gf.cross_section.cross_section(cladding_layers=[(3, 0)], cladding_offsets=[3])
w2 = gf.components.bend_euler(cross_section=xs_clad)
w2
bend_euler_0d7cc2fd: uid b14e0745, ports ['o1', 'o2'], references [], 2 polygons